/*
 * Copyright The OpenTelemetry Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { VERSION } from '../version';

const re = /^(\d+)\.(\d+)\.(\d+)(-(.+))?$/;

/**
 * Create a function to test an API version to see if it is compatible with the provided ownVersion.
 *
 * The returned function has the following semantics:
 * - Exact match is always compatible
 * - Major versions must match exactly
 *    - 1.x package cannot use global 2.x package
 *    - 2.x package cannot use global 1.x package
 * - The minor version of the API module requesting access to the global API must be less than or equal to the minor version of this API
 *    - 1.3 package may use 1.4 global because the later global contains all functions 1.3 expects
 *    - 1.4 package may NOT use 1.3 global because it may try to call functions which don't exist on 1.3
 * - If the major version is 0, the minor version is treated as the major and the patch is treated as the minor
 * - Patch and build tag differences are not considered at this time
 *
 * @param ownVersion version which should be checked against
 */
export function _makeCompatibilityCheck(
  ownVersion: string
): (globalVersion: string) => boolean {
  const acceptedVersions = new Set<string>([ownVersion]);
  const rejectedVersions = new Set<string>();

  const myVersionMatch = ownVersion.match(re);
  if (!myVersionMatch) {
    // we cannot guarantee compatibility so we always return noop
    return () => false;
  }

  const ownVersionParsed = {
    major: +myVersionMatch[1],
    minor: +myVersionMatch[2],
    patch: +myVersionMatch[3],
    prerelease: myVersionMatch[4],
  };

  // if ownVersion has a prerelease tag, versions must match exactly
  if (ownVersionParsed.prerelease != null) {
    return function isExactmatch(globalVersion: string): boolean {
      return globalVersion === ownVersion;
    };
  }

  function _reject(v: string) {
    rejectedVersions.add(v);
    return false;
  }

  function _accept(v: string) {
    acceptedVersions.add(v);
    return true;
  }

  return function isCompatible(globalVersion: string): boolean {
    if (acceptedVersions.has(globalVersion)) {
      return true;
    }

    if (rejectedVersions.has(globalVersion)) {
      return false;
    }

    const globalVersionMatch = globalVersion.match(re);
    if (!globalVersionMatch) {
      // cannot parse other version
      // we cannot guarantee compatibility so we always noop
      return _reject(globalVersion);
    }

    const globalVersionParsed = {
      major: +globalVersionMatch[1],
      minor: +globalVersionMatch[2],
      patch: +globalVersionMatch[3],
      prerelease: globalVersionMatch[4],
    };

    // if globalVersion has a prerelease tag, versions must match exactly
    if (globalVersionParsed.prerelease != null) {
      return _reject(globalVersion);
    }

    // major versions must match
    if (ownVersionParsed.major !== globalVersionParsed.major) {
      return _reject(globalVersion);
    }

    if (ownVersionParsed.major === 0) {
      if (
        ownVersionParsed.minor === globalVersionParsed.minor &&
        ownVersionParsed.patch <= globalVersionParsed.patch
      ) {
        return _accept(globalVersion);
      }

      return _reject(globalVersion);
    }

    if (ownVersionParsed.minor <= globalVersionParsed.minor) {
      return _accept(globalVersion);
    }

    return _reject(globalVersion);
  };
}

/**
 * Test an API version to see if it is compatible with this API.
 *
 * - Exact match is always compatible
 * - Major versions must match exactly
 *    - 1.x package cannot use global 2.x package
 *    - 2.x package cannot use global 1.x package
 * - The minor version of the API module requesting access to the global API must be less than or equal to the minor version of this API
 *    - 1.3 package may use 1.4 global because the later global contains all functions 1.3 expects
 *    - 1.4 package may NOT use 1.3 global because it may try to call functions which don't exist on 1.3
 * - If the major version is 0, the minor version is treated as the major and the patch is treated as the minor
 * - Patch and build tag differences are not considered at this time
 *
 * @param version version of the API requesting an instance of the global API
 */
export const isCompatible = _makeCompatibilityCheck(VERSION);
